using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class DrawAShape : MonoBehaviour
{

	#region painter
	public Texture2D sourceBaseTex;
	private Texture2D baseTex;
	
	void  Start ()
	{
		baseTex = (Texture2D) Instantiate( sourceBaseTex );
	}
	
	
	private Vector2 dragStart;
	private Vector2 dragEnd;
	
	public enum Tool 
	{
		None,
		Line,
		Brush,
		Eraser,
		Vector
	}
	
	private int tool2 = 1;
	public Samples AntiAlias = Samples.Samples4;
	public Tool tool = Tool.Brush;
	public Texture[] toolimgs;
	public Texture2D colorCircle;
	public float lineWidth = 1;
	public float strokeWidth = 1;
	public Color col = Color.white;
	public Color col2 = Color.white;
	public GUISkin gskin;
	public LineTool lineTool = new LineTool();
	public BrushTool brush = new BrushTool();
	public EraserTool eraser = new EraserTool();
	public Stroke stroke = new Stroke();
	public int zoom = 1;
	BezierPoint[] BezierPoints;
	void  OnGUI (){
		GUI.skin = gskin;
		
		GUILayout.BeginArea ( new Rect(5,5,100+baseTex.width*zoom,baseTex.height*zoom),"","Box");
		GUILayout.BeginArea ( new Rect(0,0,100,baseTex.height*zoom));
		tool2 = GUILayout.Toolbar(tool2,toolimgs,"Tool");
	
	//	tool = System.Enum.Parse (Tool,tool2.ToString ());
		
		// FIXME: Defaults to brush tool, fix enum parse above.
		tool = Tool.Brush;
		
		
		GUILayout.Label ("Drawing Options");
		GUILayout.Space (10);
		switch (tool) {
			case Tool.Line:
				GUILayout.Label ("Size "+ Mathf.Round (lineTool.width*10)/10);
				lineTool.width = GUILayout.HorizontalSlider (lineTool.width,0,40);
				col = RGBCircle (col,"",colorCircle);
				break;
			case Tool.Brush:
				GUILayout.Label ("Size "+ Mathf.Round (brush.width*10)/10);
				brush.width = GUILayout.HorizontalSlider (brush.width,0,40);
				GUILayout.Label ("Hardness " + Mathf.Round (brush.hardness*10)/10);
				brush.hardness = GUILayout.HorizontalSlider (brush.hardness,0.1f,50);
				col = RGBCircle (col,"",colorCircle);
				break;
			case Tool.Eraser:
				GUILayout.Label ("Size "+ Mathf.Round (eraser.width*10)/10);
				eraser.width = GUILayout.HorizontalSlider (eraser.width,0,50);
				GUILayout.Label ("Hardness " + Mathf.Round (eraser.hardness*10)/10);
				eraser.hardness = GUILayout.HorizontalSlider (eraser.hardness,1,50);
				break;
		}
		
		if (tool==Tool.Line) {
			stroke.enabled = GUILayout.Toggle (stroke.enabled,"Stroke");
			GUILayout.Label ("Stroke Width "+ Mathf.Round (stroke.width*10)/10);
			stroke.width = GUILayout.HorizontalSlider (stroke.width,0,lineWidth);
			GUILayout.Label ("Secondary Color");
			col2 = RGBCircle (col2,"",colorCircle);
		}
		
		GUILayout.EndArea ();
		GUI.DrawTexture ( new Rect(100,0,baseTex.width*zoom,baseTex.height*zoom),baseTex);
		GUILayout.EndArea ();
	}
	private Vector2 preDrag;
	void  Update (){
		Rect imgRect = new Rect(5+100,5,baseTex.width*zoom,baseTex.height*zoom);
		Vector2 mouse = Input.mousePosition;
		mouse.y = Screen.height-mouse.y;
		
		if (Input.GetKeyDown ("t")) {
			test ();
		}
		if (Input.GetKeyDown ("mouse 0")) {
			
			if (imgRect.Contains (mouse)) {
				if (tool==Tool.Vector) {
					var m2 = mouse-new Vector2(imgRect.x,imgRect.y);
					m2.y = imgRect.height-m2.y;
					var bz = new ArrayList (BezierPoints);
					bz.Add (new BezierPoint (m2,m2-new Vector2(50,10),m2+new Vector2(50,10)));
					BezierPoints = (BezierPoint[]) bz.ToArray();
					DrawBezier (BezierPoints,lineTool.width,col,baseTex);
				}
				
				dragStart = mouse - new Vector2 (imgRect.x,imgRect.y);
				dragStart.y =imgRect.height-dragStart.y;
				dragStart.x = Mathf.Round (dragStart.x/zoom);
				dragStart.y = Mathf.Round (dragStart.y/zoom);
				//LineStart (mouse - Vector2 (imgRect.x,imgRect.y));
				
				dragEnd = mouse - new Vector2 (imgRect.x,imgRect.y);
				dragEnd.x = Mathf.Clamp (dragEnd.x,0,imgRect.width);
				dragEnd.y = imgRect.height-Mathf.Clamp (dragEnd.y,0,imgRect.height);
				dragEnd.x = Mathf.Round ( dragEnd.x/zoom);
				dragEnd.y = Mathf.Round ( dragEnd.y/zoom);
			} else {
				dragStart=Vector3.zero;
			}
			
		}
		if (Input.GetKey ("mouse 0")) {
			if (dragStart==Vector2.zero) {
				return;
			}
			dragEnd = mouse - new Vector2 (imgRect.x,imgRect.y);
			dragEnd.x = Mathf.Clamp (dragEnd.x,0,imgRect.width);
			dragEnd.y = imgRect.height-Mathf.Clamp (dragEnd.y,0,imgRect.height);
			dragEnd.x = Mathf.Round ( dragEnd.x/zoom);
			dragEnd.y = Mathf.Round ( dragEnd.y/zoom);
			
			if (tool==Tool.Brush) {
				Brush (dragEnd,preDrag);
			}
			if (tool==Tool.Eraser) {
				Eraser (dragEnd,preDrag);
			}
			
		}
		if (Input.GetKeyUp ("mouse 0") && dragStart != Vector2.zero) {
			if (tool==Tool.Line) {
				dragEnd = mouse - new Vector2 (imgRect.x,imgRect.y);
				dragEnd.x = Mathf.Clamp (dragEnd.x,0,imgRect.width);
				dragEnd.y = imgRect.height-Mathf.Clamp (dragEnd.y,0,imgRect.height);
				dragEnd.x = Mathf.Round ( dragEnd.x/zoom);
				dragEnd.y = Mathf.Round ( dragEnd.y/zoom);
				Debug.Log ("Draw Line");
				NumSamples=AntiAlias;
				if (stroke.enabled) {
					baseTex = DrawLine (dragStart,dragEnd,lineTool.width,col,baseTex,true,col2,stroke.width);
				} else {
					baseTex = DrawLine (dragStart,dragEnd,lineTool.width,col,baseTex);
				}
			}
			dragStart=Vector2.zero;
			dragEnd=Vector2.zero;
		}
		preDrag = dragEnd;
	}
	
	void  Brush ( Vector2 p1 ,  Vector2 p2  ){
		NumSamples=AntiAlias;
		if (p2 == Vector2.zero) {
			p2 = p1;
		}
		PaintLine (p1,p2,brush.width,col,brush.hardness,baseTex);
		baseTex.Apply ();
	}
	
	void  Eraser ( Vector2 p1 ,  Vector2 p2  ){
		NumSamples=AntiAlias;
		if (p2 == Vector2.zero) {
			p2 = p1;
		}
		PaintLine (p1,p2,eraser.width,Color.white,eraser.hardness,baseTex);
		baseTex.Apply ();
	}
	
	void  test (){
		float startTime = Time.realtimeSinceStartup;
		var w = 100;
		var h = 100;
		var p1 = new BezierPoint (new Vector2(10,0),new Vector2(5,20),new Vector2(20,0));
		var p2 = new BezierPoint (new Vector2(50,10),new Vector2(40,20),new Vector2(60,-10));
		var c = new BezierCurve (p1.main,p1.control2,p2.control1,p2.main);
		p1.curve2=c;
		p2.curve1=c;
		Vector2 elapsedTime = new Vector2 ((Time.realtimeSinceStartup-startTime)*10,0);
		float startTime2 = Time.realtimeSinceStartup;
		for (var i=0;i<w*h;i++) {
			IsNearBezier (new Vector2(Random.value*80,Random.value*30),p1,p2,10);
		}
		
		Vector2 elapsedTime2 = new Vector2 ((Time.realtimeSinceStartup-startTime2)*10,0);
		Debug.Log ("Drawing took " + elapsedTime.ToString () + "  "+ elapsedTime2.ToString ());
		
	}
	
	public class LineTool {
		public float width = 1;
	}
	public class EraserTool {
		public float width = 1;
		public float hardness = 1;
	}
	public class BrushTool {
		public float width = 1;
		public float hardness = 0;
		public float spacing = 10;
	}
	public class Stroke {
		public bool enabled= false;
		public float width = 1;
	}
	
	#endregion
	
	#region gui controls
	static Color RGBSlider ( Color c ,  string label  )
	{
//		GUI.color=c;
//		GUILayout.Label (label);
//		GUI.color=Color.red;
//		c.r = GUILayout.HorizontalSlider (c.r,0,1);
//		GUI.color=Color.green;
//		c.g = GUILayout.HorizontalSlider (c.g,0,1);
//		GUI.color=Color.blue;
//		c.b = GUILayout.HorizontalSlider (c.b,0,1);
//		GUI.color=Color.white;
		return c;
	}
	
	static Color  RGBCircle ( Color c ,  string label ,  Texture2D colorCircle  ){
//		var r = GUILayoutUtility.GetAspectRect (1);
//		r.height = r.width -= 15;
//		var r2 = new Rect(r.x + r.width +5,r.y,10,r.height);
//		var hsb = new HSBColor (c);//It is much easier to work with HSB colours in this case
//		
//		
//		var cp = new Vector2 (r.x+r.width/2,r.y+r.height/2);
//		
//		if (Input.GetMouseButton (0)) {
//			var InputVector= Vector2.zero;
//			InputVector.x = cp.x - Event.current.mousePosition.x;
//			InputVector.y = cp.y - Event.current.mousePosition.y;
//			
//			var hyp = Mathf.Sqrt( (InputVector.x * InputVector.x) + (InputVector.y * InputVector.y) );
//			if (hyp <= r.width/2 + 5) {
//				hyp = Mathf.Clamp (hyp,0,r.width/2);
//				float a = Vector3.Angle(new Vector3(-1,0,0), InputVector);
//				
//				if (InputVector.y<0) {
//					a = 360 - a;
//				}
//				
//				hsb.h = a / 360;
//				hsb.s = hyp / (r.width/2);
//			}
//		}
//		
//		var hsb2 = new HSBColor (c);
//		hsb2.b = 1;
//		var c2 = hsb2.ToColor ();
//		GUI.color = c2;
//		hsb.b = GUI.VerticalSlider (r2,hsb.b,1.0f,0.0f,"BWSlider","verticalsliderthumb");
//		
//		GUI.color = Color.white * hsb.b;
//		GUI.color = new Color( GUI.color.r, GUI.color.g, GUI.color.b, 1);
//		GUI.Box (r,colorCircle,GUIStyle.none);
//		
//		var pos = (new Vector2 (Mathf.Cos (hsb.h*360*Mathf.Deg2Rad),-Mathf.Sin (hsb.h*360*Mathf.Deg2Rad))*r.width*hsb.s/2);
//		
//		GUI.color = c;
//		GUI.Box ( new Rect(pos.x-5+cp.x,pos.y-5+cp.y,10,10),"","ColorcirclePicker");
//		GUI.color = Color.white;
//		
//		c = hsb.ToColor ();
		return c;
	}
	#endregion
	
	#region drawing
	
	public enum Samples {
		None,
		Samples2,
		Samples4,
		Samples8,
		Samples16,
		Samples32,
		RotatedDisc
	}
	
	static Samples NumSamples = Samples.Samples4;
	
	static Texture2D  DrawLine ( Vector2 from ,  Vector2 to ,  float w ,  Color col ,  Texture2D tex  ){
		return DrawLine (from,to,w,col,tex,false,Color.black,0);
	}
	
	static Texture2D  DrawLine ( Vector2 from ,  Vector2 to ,  float w ,  Color col ,  Texture2D tex ,  bool stroke ,   Color strokeCol ,  float strokeWidth  ){
		w = Mathf.Round (w);//It is important to round the numbers otherwise it will mess up with the texture width
		strokeWidth = Mathf.Round (strokeWidth);
		
		var extent = w + strokeWidth;
		var stY = Mathf.Clamp (Mathf.Min (from.y,to.y)-extent,0,tex.height);//This is the topmost Y value
		var stX =  Mathf.Clamp (Mathf.Min (from.x,to.x)-extent,0,tex.width);
		var endY = Mathf.Clamp (Mathf.Max (from.y,to.y)+extent,0,tex.height);
		var endX = Mathf.Clamp (Mathf.Max (from.x,to.x)+extent,0,tex.width);//This is the rightmost Y value
		
		strokeWidth = strokeWidth/2;
		var strokeInner = (w-strokeWidth)*(w-strokeWidth);
		var strokeOuter = (w+strokeWidth)*(w+strokeWidth);
		var strokeOuter2 = (w+strokeWidth+1)*(w+strokeWidth+1);
		var sqrW = w*w;//It is much faster to calculate with squared values
		
		var lengthX = endX-stX;
		var lengthY = endY-stY;
		var start = new Vector2 (stX,stY);
	    Color[] pixels = tex.GetPixels((int)stX, (int)stY, (int)lengthX, (int)lengthY, 0);//Get all pixels
		
		for (int y=0;y<lengthY;y++) 
	    {
			for (int x=0;x<lengthX;x++) 
	        {//Loop through the pixels
				var p = new Vector2 (x,y) + start;
				var center = p + new Vector2(0.5f,0.5f);
				float dist = (center-NearestPointStrict(from,to,center)).sqrMagnitude;//The squared distance from the center of the pixels to the nearest point on the line
				if (dist<=strokeOuter2) 
				{
					var samples = Sample (p);
					var c = Color.black;
					var pc = pixels[y*(int)lengthX+x];
					for (int i=0;i<samples.Length;i++) {//Loop through the samples
						dist = (samples[i]-NearestPointStrict(from,to,samples[i])).sqrMagnitude;//The squared distance from the sample to the line
						if (stroke) {
							if (dist<=strokeOuter && dist >= strokeInner) {
								c+=strokeCol;
							} else if (dist<sqrW) {
								c+=col;
							} else {
								c+=pc;
							}
						} else {
							if (dist<sqrW) {//Is the distance smaller than the width of the line
								c+=col;
							} else {
								c+=pc;//No it wasn't, set it to be the original colour
							}
						}
					}
					c /= samples.Length;//Get the avarage colour
	                pixels[y * (int)lengthX + x] = c;
				}
			}
		}
	    tex.SetPixels((int)stX, (int)stY, (int)lengthX, (int)lengthY, pixels, 0);
		tex.Apply ();
		return tex;
	}
	
	static Texture2D  Paint ( Vector2 pos ,  float rad ,  Color col ,  float hardness ,  Texture2D tex  ){
		var start = new Vector2 (Mathf.Clamp (pos.x-rad,0,tex.width),Mathf.Clamp (pos.y-rad,0,tex.height));
		var width = rad*2;
		var end = new Vector2 (Mathf.Clamp (pos.x+rad,0,tex.width),Mathf.Clamp (pos.y+rad,0,tex.height));
		var widthX = Mathf.Round (end.x-start.x);
		var widthY = Mathf.Round (end.y-start.y);
		var sqrRad = rad*rad;
		var sqrRad2 = (rad+1)*(rad+1);
	    Color[] pixels = tex.GetPixels((int)start.x, (int)start.y, (int)widthX, (int)widthY, 0);
		
		for (var y=0;y<widthY;y++) 
	    {
			for (var x=0;x<widthX;x++) 
	        {
				var p = new Vector2 (x,y) + start;
				var center = p + new Vector2(0.5f,0.5f);
				float dist = (center-pos).sqrMagnitude;
				if (dist>sqrRad2) {
					continue;
				}
				var samples = Sample (p);
				var c = Color.black;
				for (var i=0;i<samples.Length;i++) 
	            {
					dist = GaussFalloff (Vector2.Distance(samples[i],pos),rad) * hardness;
					if (dist>0) 
	                {
						c += Color.Lerp (pixels[y*(int)widthX+x],col,dist);
					}
	                else 
	                {
						c+=pixels[y*(int)widthX+x];
					}
				}
				c /= samples.Length;
				
				pixels[y*(int)widthX+x]=c;
			}
		}
	
	    tex.SetPixels((int)start.x, (int)start.y, (int)widthX, (int)widthY, pixels, 0);
		return tex;
	}
	
	static Texture2D  PaintLine ( Vector2 from ,  Vector2 to ,  float rad ,  Color col ,  float hardness ,  Texture2D tex  )
	{
		var width = rad*2;
	
	    var extent = rad;
	    var stY = Mathf.Clamp(Mathf.Min(from.y, to.y) - extent, 0, tex.height);
	    var stX = Mathf.Clamp(Mathf.Min(from.x, to.x) - extent, 0, tex.width);
	    var endY = Mathf.Clamp(Mathf.Max(from.y, to.y) + extent, 0, tex.height);
	    var endX = Mathf.Clamp(Mathf.Max(from.x, to.x) + extent, 0, tex.width);
	
	    var lengthX = endX - stX;
	    var lengthY = endY - stY;
	
	    var sqrRad = rad * rad;
	    var sqrRad2 = (rad + 1) * (rad + 1);
	    Color[] pixels = tex.GetPixels((int)stX, (int)stY, (int)lengthX, (int)lengthY, 0);
	    var start = new Vector2(stX, stY);
		//Debug.Log (widthX + "   "+ widthY + "   "+ widthX*widthY);
		for (int y=0;y<(int)lengthY;y++) 
	    {
			for (int x=0;x<(int)lengthX;x++) 
	        {
				var p = new Vector2 (x,y) + start;
	            var center = p + new Vector2(0.5f, 0.5f);
				float dist = (center-NearestPointStrict(from,to,center)).sqrMagnitude;
				if (dist>sqrRad2) {
					continue;
				}
				dist = GaussFalloff (Mathf.Sqrt(dist),rad) * hardness;
				//dist = (samples[i]-pos).sqrMagnitude;
	            Color c;
				if (dist>0) {
	                c = Color.Lerp(pixels[y * (int)lengthX + x], col, dist);
				} else {
	                c = pixels[y * (int)lengthX + x];
				}
	
	            pixels[y * (int)lengthX + x] = c;
			}
		}
	    tex.SetPixels((int)start.x, (int)start.y, (int)lengthX, (int)lengthY, pixels, 0);
		return tex;
	}
	
	internal class BezierPoint 
	{
		internal Vector2 main;
		internal Vector2 control1;//Think of as left
		internal Vector2 control2;//Right
		//Rect rect;
		internal BezierCurve curve1;//Left
		internal BezierCurve curve2;//Right
		
		internal BezierPoint ( Vector2 m ,  Vector2 l ,  Vector2 r  ){
			main = m;
			control1 = l;
			control2 = r;
		}
	}
	
	internal class BezierCurve 
	{
		internal Vector2[] points;
		internal float aproxLength;
		internal Rect rect;
		internal Vector2  Get ( float t  )
		{
			int t2 = (int) Mathf.Round (t*(points.Length-1));
			return points[t2];
		}
	
		void  Init ( Vector2 p0 ,  Vector2 p1 ,  Vector2 p2 ,  Vector2 p3  )
		{
			
			Vector2 topleft = new Vector2(Mathf.Infinity,Mathf.Infinity);
			Vector2 bottomright = new Vector2(Mathf.NegativeInfinity,Mathf.NegativeInfinity);
			
			topleft.x = Mathf.Min (topleft.x,p0.x);
			topleft.x = Mathf.Min (topleft.x,p1.x);
			topleft.x = Mathf.Min (topleft.x,p2.x);
			topleft.x = Mathf.Min (topleft.x,p3.x);
			
			topleft.y = Mathf.Min (topleft.y,p0.y);
			topleft.y = Mathf.Min (topleft.y,p1.y);
			topleft.y = Mathf.Min (topleft.y,p2.y);
			topleft.y = Mathf.Min (topleft.y,p3.y);
			
			bottomright.x = Mathf.Max (bottomright.x,p0.x);
			bottomright.x = Mathf.Max (bottomright.x,p1.x);
			bottomright.x = Mathf.Max (bottomright.x,p2.x);
			bottomright.x = Mathf.Max (bottomright.x,p3.x);
			
			bottomright.y = Mathf.Max (bottomright.y,p0.y);
			bottomright.y = Mathf.Max (bottomright.y,p1.y);
			bottomright.y = Mathf.Max (bottomright.y,p2.y);
			bottomright.y = Mathf.Max (bottomright.y,p3.y);
			
			rect = new Rect(topleft.x,topleft.y,bottomright.x-topleft.x,bottomright.y-topleft.y);
			
			
			var ps = new List<Vector2>();
			
			var point1  = CubicBezier (0,p0,p1,p2,p3);
			var point2  = CubicBezier (0.05f,p0,p1,p2,p3);
			var point3  = CubicBezier (0.1f,p0,p1,p2,p3);
			var point4  = CubicBezier (0.15f,p0,p1,p2,p3);
			
			var point5  = CubicBezier (0.5f,p0,p1,p2,p3);
			var point6  = CubicBezier (0.55f,p0,p1,p2,p3);
			var point7  = CubicBezier (0.6f,p0,p1,p2,p3);
			
			aproxLength = Vector2.Distance(point1,point2) + Vector2.Distance (point2,point3) + Vector2.Distance (point3,point4)  + Vector2.Distance (point5,point6)  + Vector2.Distance (point6,point7);
			
			Debug.Log (Vector2.Distance(point1,point2) + "     " + Vector2.Distance(point3,point4) + "   " + Vector2.Distance(point6, point7));
			aproxLength*= 4;
			
			float a2 = 0.5f/aproxLength;//Double the amount of points since the approximation is quite bad
			for (float i = 0 ;i<1;i+=a2) 
	        {
				ps.Add (CubicBezier (i,p0,p1,p2,p3));
			}
			
			points = ps.ToArray();
		}
		
		 internal BezierCurve ( Vector2 main ,  Vector2 control1 ,  Vector2 control2 ,  Vector2 end  )
	     {
			Init (main,control1,control2,end);
		}
	}
	
	static void  DrawBezier ( BezierPoint[] points ,  float rad ,  Color col ,  Texture2D tex  )
	{
		rad = Mathf.Round (rad);//It is important to round the numbers otherwise it will mess up with the texture width
		
		if (points.Length<=1) 
			return;
		
		Vector2 topleft = new Vector2(Mathf.Infinity,Mathf.Infinity);
		Vector2 bottomright = new Vector2(0,0);
		
		for (int i=0;i<points.Length-1;i++) 
	    {
	        Vector2 main = points[i].main;
	        Vector2 control2 = points[i].control2;
	        Vector2 control1 = points[i + 1].control1;
	        Vector2 main2 = points[i + 1].main;
			BezierCurve curve = new BezierCurve( main, control2, control1, main2 );
			points[i].curve2 = curve;
			points[i+1].curve1 = curve;
			
			topleft.x = Mathf.Min (topleft.x,curve.rect.x);
		
			topleft.y = Mathf.Min (topleft.y,curve.rect.y);
		
			bottomright.x = Mathf.Max (bottomright.x,curve.rect.x+curve.rect.width);
		
			bottomright.y = Mathf.Max (bottomright.y,curve.rect.y+curve.rect.height);
		}
		
		topleft -= new Vector2(rad,rad);
		bottomright += new Vector2(rad,rad);
		
		var start = new Vector2 (Mathf.Clamp (topleft.x,0,tex.width),Mathf.Clamp (topleft.y,0,tex.height));
	    var width = new Vector2(Mathf.Clamp(bottomright.x - topleft.x, 0, tex.width - start.x), Mathf.Clamp(bottomright.y - topleft.y, 0, tex.height - start.y));
	
	    Color[] pixels = tex.GetPixels((int)start.x, (int)start.y, (int)width.x, (int)width.y, 0);
		
		for (var y=0;y<width.y;y++) 
	    {
	        for (var x = 0; x < width.x; x++) 
	        {
				var p = new Vector2(x+start.x,y+start.y);
				if (!IsNearBeziers (p,points,rad+2))
	            {
					continue;
				}
				
				var samples = Sample (p);
				var c = Color.black;
				var pc = pixels[y*(int)width.x+x];//Previous pixel color
				for (var i=0;i<samples.Length;i++) 
				{
					if (IsNearBeziers (samples[i],points,rad)) 
					{
						c+= col;
					} else {
						c+= pc;
					}
				}
				
				c /= samples.Length;
				
				pixels[y*(int)width.x+x] = c;
			}
		}
	
	    tex.SetPixels((int)start.x, (int)start.y, (int)width.x, (int)width.y, pixels, 0);
		tex.Apply ();
	}
	
	static void AddP(List<Vector2> tmpList, Vector2 p, float ix, float iy )
	{
	    var x = p.x + ix;
	    var y = p.y + iy;
	    tmpList.Add(new Vector2(x, y));
	}
	
	static Vector2[] Sample ( Vector2 p  )
	{
		List<Vector2> tmpList = new List<Vector2>(32);
	
		switch (NumSamples) 
	    {
	        case Samples.None:
				AddP(tmpList,p,0.5f,0.5f);
	            break;
	
	        case Samples.Samples2:
	            AddP(tmpList,p,0.25f,0.5f);
	            AddP(tmpList,p,0.75f,0.5f);
	            break;
	
	        case Samples.Samples4: 
	            AddP(tmpList,p,0.25f,0.5f);
	            AddP(tmpList,p,0.75f,0.5f);
	            AddP(tmpList,p,0.5f,0.25f);
	            AddP(tmpList,p,0.5f,0.75f);
	            break;
	
	        case Samples.Samples8: 
	            AddP(tmpList,p,0.25f,0.5f);
	            AddP(tmpList,p,0.75f,0.5f);
	            AddP(tmpList,p,0.5f,0.25f);
	            AddP(tmpList,p,0.5f,0.75f);
	
	            AddP(tmpList,p,0.25f,0.25f);
	            AddP(tmpList,p,0.75f,0.25f);
	            AddP(tmpList,p,0.25f,0.75f);
	            AddP(tmpList,p,0.75f,0.75f);
	            break;
	        case Samples.Samples16: 	
	            AddP(tmpList,p,0,0);
	            AddP(tmpList,p,0.3f,0);
	            AddP(tmpList,p,0.7f,0);
	            AddP(tmpList,p,1,0);
	
	            AddP(tmpList,p,0,0.3f);
	            AddP(tmpList,p,0.3f,0.3f);
	            AddP(tmpList,p,0.7f,0.3f);
	            AddP(tmpList,p,1,0.3f);
	
	            AddP(tmpList,p,0,0.7f);
	            AddP(tmpList,p,0.3f,0.7f);
	            AddP(tmpList,p,0.7f,0.7f);
	            AddP(tmpList,p,1,0.7f);
	
	            AddP(tmpList,p,0,1);
	            AddP(tmpList,p,0.3f,1);
	            AddP(tmpList,p,0.7f,1);
	            AddP(tmpList,p,1,1);
				break;
	
	        case Samples.Samples32:
	            AddP(tmpList,p,0,0);
	            AddP(tmpList,p,1,0);
	            AddP(tmpList,p,0,1);
	            AddP(tmpList,p,1,1);
	
	            AddP(tmpList,p,0.2f,0.2f);
	            AddP(tmpList,p,0.4f,0.2f);
	            AddP(tmpList,p,0.6f,0.2f);
	            AddP(tmpList,p,0.8f,0.2f);
	
	            AddP(tmpList,p,0.2f,0.4f);
	            AddP(tmpList,p,0.4f,0.4f);
	            AddP(tmpList,p,0.6f,0.4f);
	            AddP(tmpList,p,0.8f,0.4f);
	
	            AddP(tmpList,p,0.2f,0.6f);
	            AddP(tmpList,p,0.4f,0.6f);
	            AddP(tmpList,p,0.6f,0.6f);
	            AddP(tmpList,p,0.8f,0.6f);
	
	            AddP(tmpList,p,0.2f,0.8f);
	            AddP(tmpList,p,0.4f,0.8f);
	            AddP(tmpList,p,0.6f,0.8f);
	            AddP(tmpList,p,0.8f,0.8f);
	
	            AddP(tmpList,p,0.5f,0);
	            AddP(tmpList,p,0.5f,1);
	            AddP(tmpList,p,0,0.5f);
	            AddP(tmpList,p,1,0.5f);
				
	            AddP(tmpList,p,0.5f,0.5f);
	            break;
	        case Samples.RotatedDisc:
	            AddP(tmpList,p,0,0);
	            AddP(tmpList,p,1,0);
	            AddP(tmpList,p,0,1);
	            AddP(tmpList,p,1,1);
	
	            Vector2 pq = new Vector2(p.x + 0.5f, p.y + 0.5f);
	            AddP(tmpList,pq,0.258f,0.965f);//Sin (75°) && Cos (75°)
	            AddP(tmpList,pq,-0.965f,-0.258f);
	            AddP(tmpList,pq,0.965f,0.258f);
	            AddP(tmpList,pq,0.258f,-0.965f);
	    		break;
		}
	
	    return tmpList.ToArray();
	}
	
	#endregion
	
	#region mathfx
	
	static float Hermite ( float start ,   float end ,   float value  ){
		return Mathf.Lerp(start, end, value * value * (3.0f - 2.0f * value));
	}
		
	static float Sinerp ( float start ,   float end ,   float value  ){
		return Mathf.Lerp(start, end, Mathf.Sin(value * Mathf.PI * 0.5f));
	}
	
	static float Coserp ( float start ,   float end ,   float value  ){
		return Mathf.Lerp(start, end, 1.0f - Mathf.Cos(value * Mathf.PI * 0.5f));
	}
	 
	static float Berp ( float start ,   float end ,   float value  ){
		value = Mathf.Clamp01(value);
		value = (Mathf.Sin(value * Mathf.PI * (0.2f + 2.5f * value * value * value)) * Mathf.Pow(1 - value, 2.2f) + value) * (1 + (1.2f * (1 - value)));
		return start + (end - start) * value;
	}
		
	static float SmoothStep ( float x ,   float min ,   float max  ){
		x = Mathf.Clamp (x, min, max);
		var v1= (x-min)/(max-min);
		var v2= (x-min)/(max-min);
		return -2*v1 * v1 *v1 + 3*v2 * v2;
	}
	 
	static float Lerp ( float start ,   float end ,   float value  ){
		return ((1.0f - value) * start) + (value * end);
	}
	 
	static Vector3 NearestPoint ( Vector3 lineStart ,   Vector3 lineEnd ,   Vector3 point  ){
		var lineDirection= Vector3.Normalize(lineEnd-lineStart);
		var closestPoint= Vector3.Dot((point-lineStart),lineDirection)/Vector3.Dot(lineDirection,lineDirection);
		return lineStart+(closestPoint*lineDirection);
	}
	 
	static Vector3 NearestPointStrict ( Vector3 lineStart ,   Vector3 lineEnd ,   Vector3 point  ){
		var fullDirection= lineEnd-lineStart;
		var lineDirection= Vector3.Normalize(fullDirection);
		var closestPoint= Vector3.Dot((point-lineStart),lineDirection)/Vector3.Dot(lineDirection,lineDirection);
		return lineStart+(Mathf.Clamp(closestPoint,0.0f,Vector3.Magnitude(fullDirection))*lineDirection);
	}
	
	static Vector2 NearestPointStrict ( Vector2 lineStart ,   Vector2 lineEnd ,   Vector2 point  ){
		var fullDirection= lineEnd-lineStart;
		var lineDirection= Normalize(fullDirection);
		var closestPoint= Vector2.Dot((point-lineStart),lineDirection)/Vector2.Dot(lineDirection,lineDirection);
		return lineStart+(Mathf.Clamp(closestPoint,0.0f,fullDirection.magnitude)*lineDirection);
	}
	
	
	
	static float Bounce ( float x  ){
		return Mathf.Abs(Mathf.Sin(6.28f*(x+1)*(x+1)) * (1-x));
	}
		
	// test for value that is near specified float (due to floating point inprecision)
	// all thanks to Opless for this!
	static bool Approx ( float val ,   float about ,   float range  ){
		 return ( ( Mathf.Abs(val - about) < range) );
	}
	 
	// test if a Vector3 is close to another Vector3 (due to floating point inprecision)
	// compares the square of the distance to the square of the range as this 
	// avoids calculating a square root which is much slower than squaring the range
	static bool Approx ( Vector3 val ,   Vector3 about ,   float range  ){
		return ( (val - about).sqrMagnitude < range*range);
	}
	static float GaussFalloff ( float distance  ,   float inRadius  ){
		return Mathf.Clamp01 (Mathf.Pow (360.0f, -Mathf.Pow (distance / inRadius, 2.5f) - 0.01f));
	}
	// CLerp - Circular Lerp - is like lerp but handles the wraparound from 0 to 360.
	// This is useful when interpolating eulerAngles and the object
	// crosses the 0/360 boundary.  The standard Lerp function causes the object
	// to rotate in the wrong direction and looks stupid. Clerp fixes that.
	static float Clerp ( float start ,   float end ,   float value  ){
	   var min= 0.0f;
	   var max = 360.0f;
	   var half = Mathf.Abs((max - min) / 2.0f);//half the distance between min and max
	   var retval = 0.0f;
	   var diff = 0.0f;
		
	   if((end - start) < -half){
		   diff = ((max - start)+end)*value;
		   retval =  start+diff;
	   }
	   else if((end - start) > half){
		   diff = -((max - end)+start)*value;
		   retval =  start+diff;
	   }
	   else retval =  start+(end-start)*value;
		
	   return retval;
	}
	
	
	//======= NEW =========//
	
	
	static Vector2 RotateVector(Vector2 vector, float rad)
	{
		rad *= Mathf.Deg2Rad;
		var res = new Vector2 ((vector.x * Mathf.Cos(rad)) - (vector.y * Mathf.Sin(rad)),(vector.x * Mathf.Sin(rad)) + (vector.y * Mathf.Cos(rad)));
	    return res;
	}
	
	static Vector2  IntersectPoint ( Vector2 start1 ,  Vector2 start2 ,  Vector2 dir1 ,  Vector2 dir2  ){
		if (dir1.x==dir2.x) {
			return Vector2.zero;
		}
		
		var h1 = dir1.y/dir1.x;
	    var h2 = dir2.y / dir2.x;
		
		if (h1==h2) 
	    {
			return Vector2.zero;
		}
		
		var line1 = new Vector2 (h1,start1.y-start1.x*h1);
		var line2 = new Vector2 (h2,start2.y-start2.x*h2);
		
		var y1 = line2.y-line1.y;
		var x1 = line1.x-line2.x;
		
		var x2 = y1 / x1;
		
		var y2 = line1.x*x2 + line1.y;
		return new Vector2(x2,y2);
	}
	
	static Vector2 ThreePointCircle ( Vector2 a1 ,  Vector2 a2 ,  Vector2 a3  )
	{
		var dir = a2-a1;
		dir /= 2;
		var b1 = a1+dir;
		dir = RotateVector (dir,90);
	    var l1 = dir;
		
		dir = a3-a2;
		dir /= 2;
	    var b2 = a2 + dir;
		dir = RotateVector (dir,90);
	    var l2 = dir;
	    var p = IntersectPoint(b1, b2, l1, l2);
		return p;
	}
	
	//===== Bezier ====== //
	
	static Vector2  CubicBezier ( float t ,  Vector2 p0 ,  Vector2 p1 ,  Vector2 p2 ,  Vector2 p3  )
	{
	    // FIXME: fix bezier curve algorithm.
	/*	t = Mathf.Clamp01 (t);
		var t2 = 1-t;
	    return Mathf.Pow(t2, 3) * p0 + 3 * Mathf.Pow(t2, 2) * t * p1 + 3 * t2 * Mathf.Pow(t, 2) * p2 + Mathf.Pow(t, 3) * p3;
	 */
	    return Vector2.zero;
	}
	
	static Vector2 NearestPointOnBezier(Vector2 p, BezierCurve c, float accuracy, bool doubleAc)
	{
		 float minDist = Mathf.Infinity;
		float minT = 0;
		Vector2 minP = Vector2.zero;
		for (float i = 0 ;i<1;i+=accuracy) 
	    {
			var point  = c.Get (i);
			float d = (p - point).sqrMagnitude;
			if (d<minDist) {
				minDist=d;
				minT = i;
				minP = point;
			}
		}
		
		if (!doubleAc) 
	    {
			return minP;
		}
	
	    float st = Mathf.Clamp01(minT - accuracy);
		float en = Mathf.Clamp01 (minT+accuracy);
		
		
		for (var i=st;i<en;i+=accuracy/10) 
	    {
			var point  = c.Get (i);
			float d= (p - point).sqrMagnitude;
			if (d<minDist) 
	        {
				minDist=d;
				minT = i;
				minP = point;
			}
		}
		
		return minP;
	}
	
	static bool IsNearBezierTest ( Vector2 p ,  BezierCurve c ,  float accuracy ,  float maxDist  ){
		Vector2 prepoint = c.Get (0);
		for (float i = accuracy;i<1;i+=accuracy) 
	    {
			var point  = c.Get (i);
			float d = (p - point).sqrMagnitude;
			float d2 = (prepoint - point + new Vector2(maxDist,maxDist)).sqrMagnitude;
			if (d<=d2*2) 
				return true;
		}
		
		return false;
	}
	
	static Vector2  NearestPointOnBezier ( Vector2 p ,  Vector2 p0 ,  Vector2 p1 ,  Vector2 p2 ,  Vector2 p3  )
	{
		float minDist = Mathf.Infinity;
		float minT = 0;
	    Vector2 minP = Vector2.zero;
		for (float i = 0 ;i<1;i+=0.01f) 
	    {
			var point  = CubicBezier (i,p0,p1,p2,p3);
			float d = (p - point).sqrMagnitude;
			if (d<minDist) {
				minDist=d;
				minT = i;
				minP = point;
			}
		}
		
		float st = Mathf.Clamp01 (minT-0.01f);
	    float en = Mathf.Clamp01(minT + 0.01f);
		
		for (var i=st;i<en;i+=0.001f) 
	    {
			var point  = CubicBezier (i,p0,p1,p2,p3);
			var d= (p - point).sqrMagnitude;
			if (d<minDist) 
	        {
				minDist=d;
				minT = i;
				minP = point;
			}
		}
		
		return minP;
		
	}
	
	static bool  IsNearBezier ( Vector2 p ,  BezierPoint point1 ,  BezierPoint point2 ,  float rad  )
	{
		if (point1.curve2 != point2.curve1) {
			Debug.LogError ("Curves Not The Same");
			return false;
		}
		
		BezierCurve curve = point1.curve2;
		
		var r = curve.rect;
		r.x-=rad;
		r.y-=rad;
		r.width+=rad*2;
		r.height+=rad*2;
		
		if (!r.Contains (p)) {
			return false;
		}
		
		var nearest = NearestPointOnBezier (p,curve,0.1f,false);
		
		var sec = point1.curve2.aproxLength/10;
		
		if ((nearest-p).sqrMagnitude>=(sec*3)*(sec*3)) {
			return false;
		}
		
		nearest = NearestPointOnBezier (p,curve,0.01f,true);
		
		if ((nearest-p).sqrMagnitude<=rad*rad) {
			return true;
		}
		
		return false;
	}
	
	static bool  IsNearBeziers ( Vector2 p ,  BezierPoint[] points ,  float rad  )
	{
		for (var i=0;i<points.Length-1;i++) 
	    {
			if (IsNearBezier (p,points[i],points[i+1],rad)) 
	        {
				return true;
			}
		}
		return false;
	}
	
	//====== End Bezier ========//
	
	static Vector2  NearestPointOnCircle ( Vector2 p ,  Vector2 center ,  float w  )
	{
		Vector2 dir = p-center;
		dir = Normalize (dir);
		dir *= w;
		return center+dir;
	}
	static Vector2 Normalize(Vector2 p)
	{
		float mag = p.magnitude;
		return p/mag;
	}
	
	#endregion
	
}

